﻿//#define VERBOSE
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using RayDen.Library.Components.SystemComponents;
using RayDen.RayEngine.Core;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Threads;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Data;
using RayDen.RayEngine.Managers;
using RayDen.RayEngine.Samplers;

namespace RayDen.RayEngine
{
    public abstract class GenericDeviceRenderThreadBase<TPathProcessor, TPathAggregator> : RenderThread
        where TPathProcessor : IPathProcessor
    {
        protected IIntersectionEngine intersectionDevice;
        protected TPathProcessor _pathRayProcessors;
        protected TPathAggregator pixelDevice;

        protected RayBuffer[] rayBuffers;
        protected SampleBuffer sampleBuffer;
        protected Task renderThread;

        protected int width, height;
        protected IFrameManager frameManager;

        protected GenericDeviceRenderThreadBase(int index, RayEngineScene scene)
            : base(index, scene)
        {
        }
        protected GenericDeviceRenderThreadBase(int index,
                                       IIntersectionEngine device,
                                       RayEngineScene scn,
                                       bool lowLatency,
                                       TPathAggregator pixelDevice,
                                       SamplingContext sc, IFrameManager fM)
            : base(index, scn)
        {
            this.pixelDevice = pixelDevice;
            this.frameManager = fM;
            Init(device, lowLatency, pixelDevice, sc);
        }

        protected void Init(IIntersectionEngine device, bool lowLatency, TPathAggregator pixelDevice, SamplingContext samplers)
        {
            this.sampler = samplers.PrimarySpaceSampler;
            this.Film = (IImageFilm) pixelDevice;
            intersectionDevice = device;
            width = sampler.Width;
            height = sampler.Height;
            var rayBufferSize = lowLatency ? (RayBuffer.RayBufferSize / 8) : RayBuffer.RayBufferSize;
            rayBuffers = new RayBuffer[DeviceRenderBufferCount];
            this.pixelDevice = pixelDevice;
            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                rayBuffers[i] = new RayBuffer(rayBufferSize);
                rayBuffers[i].UserData = threadIndex;
            }
            _pathRayProcessors = this.CreatePathBuffer(scene.MaxPaths, (RayEngineScene)scene, pixelDevice, samplers);


            renderThread = null;
        }

        private CancellationTokenSource cancelToken;


        protected abstract TPathProcessor CreatePathBuffer(int maxPath, RayEngineScene scene, TPathAggregator pixelDevice, SamplingContext sc);

        public override void Start()
        {
            base.Start();

            sampler.Init(width, height);


            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                rayBuffers[i].Reset();
                rayBuffers[i].ResetUserData();
                rayBuffers[i].UserData = (i);
            }
            _pathRayProcessors.ReInit();
            cancelToken = new CancellationTokenSource();
            // Create the thread for the rendering
            intersectionDevice.Start();
            this.renderThread = Task.Factory.StartNew(() => RenderThreadImpl(cancelToken.Token, this), cancelToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
        }
        public override void Interrupt()
        {
            if (this.renderThread.Status == TaskStatus.Running)
            {
                cancelToken.Cancel();
            }
        }
        public override void Stop()
        {
            try
            {
                if (this.renderThread.Status == TaskStatus.Running)
                {
                    cancelToken.Cancel();
                    while (this.renderThread.Status == TaskStatus.Running)
                    {

                    }
                }
             
            }
            finally
            {
                this.renderThread.Dispose();
                this.renderThread = null;
                base.Stop();
            }
        }
        public override void ClearPaths()
        {
            _pathRayProcessors.ClearPaths();
        }

        public override int GetPass()
        {
            return sampler.GetPass();
        }

        public override void Restart()
        {
            if (started)
                return;

          
            if (this.renderThread != null)
            {
                this.renderThread.Dispose();
                this.renderThread = null;
            }
            if (cancelToken != null)
            {
                cancelToken.Dispose();
            }
            _pathRayProcessors.ClearPaths();
            this.Start();

        }

        protected virtual void ThreadJob(CancellationToken t, GenericDeviceRenderThreadBase<TPathProcessor, TPathAggregator> renderThread)
        {
            Tracer.TraceLine("CL Render Thread {0} started", renderThread.threadIndex);
            if (!started)
                return;

#if VERBOSE
            try
            {
#endif
                var todoBuffers = new Queue<RayBuffer>();
                for (var i = 0; i < DeviceRenderBufferCount; i++)
                    todoBuffers.Enqueue(renderThread.rayBuffers[i]);

                while (true)
                {
                    if ((frameManager.FrameReady || t.IsCancellationRequested))
                    {
                        break;
                    }
                    while (todoBuffers.Count > 0)
                    {
                        var rayBuffer = todoBuffers.Dequeue();
                        rayBuffer.Reset();
                        renderThread._pathRayProcessors.FillRayBuffer(rayBuffer);
                        renderThread.intersectionDevice.PushRayBuffer(rayBuffer, renderThread.threadIndex);
                    }
                    var rayBuff = renderThread.intersectionDevice.PopRayBuffer(renderThread.threadIndex);
                    if (rayBuff != null && rayBuff.UserData == renderThread.threadIndex)
                    {
                        renderThread._pathRayProcessors.AdvancePaths(rayBuff);
                        todoBuffers.Enqueue(rayBuff);
                    }
                }
                started = false;
#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine("Exiting rendering : Reason \n\r{0}", ex);
            }
#endif
            frameManager.Complete();
            Tracer.TraceLine("CL Render Thread {0} exited", renderThread.threadIndex);
        }


        protected static void RenderThreadImpl(CancellationToken t, GenericDeviceRenderThreadBase<TPathProcessor, TPathAggregator> renderThread)
        {
            renderThread.ThreadJob(t, renderThread);
        }
    }


    public abstract class DeviceRenderThreadBase<TGather, TEval, TContext> : RenderThread
        where TGather : PathBufferBase
        where TEval : PathBufferBase
    {
        protected IIntersectionEngine intersectionDevice;
        protected PathBufferBase[] _pathRayProcessors;
        protected RayBuffer[] rayBuffers;
        protected SampleBuffer sampleBuffer;
        protected Task renderThread;

        protected int width, height;

        protected DeviceRenderThreadBase(int index, RayEngineScene scene) : base(index, scene) { }
        protected DeviceRenderThreadBase(int index,
                                       IIntersectionEngine device,
                                       RayEngineScene scn,
                                       bool lowLatency,
                                       ImageFilmBase pixelDevice,
                                       SamplingContext sc)
            : base(index, scn)
        {
            Init(device, lowLatency, pixelDevice, sc);
        }

        protected void Init(IIntersectionEngine device, bool lowLatency, ImageFilmBase pixelDevice, SamplingContext sc)
        {
            var _DeviceRenderBufferCount = 2;
            intersectionDevice = device;
            // Sample buffer
            var sampleBufferSize = lowLatency ? (SAMPLE_BUFFER_SIZE / 4) : SAMPLE_BUFFER_SIZE;
            sampleBuffer = new SampleBuffer(sampleBufferSize);

            width = pixelDevice.Width;
            height = pixelDevice.Height;
            var rayBufferSize = lowLatency ? (RayBuffer.RayBufferSize / 8) : RayBuffer.RayBufferSize;
            this.sampler = sc.PrimarySpaceSampler;
            _pathRayProcessors = new PathBufferBase[2];
            rayBuffers = new RayBuffer[_DeviceRenderBufferCount];

            for (int i = 0; i < _DeviceRenderBufferCount; i++)
            {
                rayBuffers[i] = new RayBuffer(rayBufferSize);
                rayBuffers[i].UserData = (threadIndex);

            }

            _pathRayProcessors[0] = this.CreateGatherPathBuffer(scene.MaxPaths, (RayEngineScene)scene, pixelDevice,  sc);
            _pathRayProcessors[1] = this.CreateEvalPathBuffer(scene.MaxPaths, (RayEngineScene)scene, pixelDevice,    sc);


            renderThread = null;
        }

        private CancellationTokenSource cancelToken;


        protected abstract TGather CreateGatherPathBuffer(int maxPath, RayEngineScene scene, ImageFilmBase pixelDevice,
                                                           SamplingContext sc);

        protected abstract TEval CreateEvalPathBuffer(int maxPath, RayEngineScene scene, ImageFilmBase pixelDevice,
                                                           SamplingContext sc);

        public override void Start()
        {
            base.Start();

            sampler.Init(width, height);

            sampleBuffer.Reset();
            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                rayBuffers[i].Reset();
                rayBuffers[i].ResetUserData();
                rayBuffers[i].UserData = (i);
                _pathRayProcessors[i].ReInit();
            }
            cancelToken = new CancellationTokenSource();
            // Create the thread for the rendering
            this.renderThread = Task.Factory.StartNew(() => RenderThreadImpl(cancelToken.Token, this), cancelToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Current);
            intersectionDevice.Start();
        }
        public override void Interrupt()
        {
            if (this.renderThread.Status == TaskStatus.Running)
            {
                cancelToken.Cancel();
            }
        }
        public override void Stop()
        {
            if (this.renderThread.Status == TaskStatus.Running)
            {
                cancelToken.Cancel();
            }
            this.renderThread.Dispose();
            this.renderThread = null;
            base.Stop();
        }
        public override void ClearPaths()
        {
            for (int i = 0; i < DeviceRenderBufferCount; i++)
            {
                _pathRayProcessors[i].ClearPaths();
            }
        }



        public override int GetPass()
        {
            return sampler.GetPass();
        }

        protected static void RenderThreadImpl(CancellationToken t, DeviceRenderThreadBase<TGather, TEval, TContext> renderThread)
        {
            if (!renderThread.started)
                return;
            //do things
            Tracer.TraceLine("CL Render Thread {0} started", renderThread.threadIndex);

#if VERBOSE
            try
            {
#endif
                var todoBuffers = new Queue<RayBuffer>();
                for (var i = 0; i < DeviceRenderBufferCount; i++)
                    todoBuffers.Enqueue(renderThread.rayBuffers[i]);

                while (!t.IsCancellationRequested)
                {
                    while (todoBuffers.Count > 0)
                    {
                        var rayBuffer = todoBuffers.Dequeue();
                        rayBuffer.Reset();
                        renderThread._pathRayProcessors[0].FillRayBuffer(rayBuffer);
                        renderThread.intersectionDevice.PushRayBuffer(rayBuffer, renderThread.threadIndex);
                    }
                    var rayBuff = renderThread.intersectionDevice.PopRayBuffer(renderThread.threadIndex);
                    if (rayBuff != null && rayBuff.UserData == renderThread.threadIndex)
                    {
                        renderThread._pathRayProcessors[0].AdvancePaths(rayBuff);
                        todoBuffers.Enqueue(rayBuff);
                    }
                }
#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine("Exiting rendering : Reason \n\r{0}", ex);
                Tracer.TraceLine(ex.StackTrace);
            }
#endif
            Tracer.TraceLine("CL Render Thread {0} exited", renderThread.threadIndex);
        }
    }

}